package com.xyhy.starter.redis.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.data.redis.core.*;
import com.xyhy.core.jackson.utils.JacksonUtil;
import com.xyhy.core.utils.jdk.CollectorUtils;
import com.xyhy.core.utils.spring.SpringContextUtil;
import com.xyhy.starter.redis.RedisConfig;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class RedisUtil {

  public static RedisTemplate<String, Object> getObjectTemplate() {
    return SpringContextUtil.getBean(RedisConfig.OBJ_TEMPLATE, RedisTemplate.class);
  }

  public static ValueOperations<String, Object> opsForValue() {
    return getObjectTemplate().opsForValue();
  }

  public static HashOperations<String, String, Object> opsForHash() {
    return getObjectTemplate().opsForHash();
  }

  public static ListOperations<String, Object> opsForList() {
    return getObjectTemplate().opsForList();
  }

  public static SetOperations<String, Object> opsForSet() {
    return getObjectTemplate().opsForSet();
  }

  public static ZSetOperations<String, Object> opsForZSet() {
    return getObjectTemplate().opsForZSet();
  }


  public static boolean hasKey(String key) {
    return BooleanUtil.isTrue(getObjectTemplate().hasKey(key));
  }

  public static boolean absentKey(String key) {
    return !hasKey(key);
  }

  public static Boolean delete(String key) {
    return getObjectTemplate().delete(key);
  }

  public static Long delete(List<String> keys) {
    return getObjectTemplate().delete(keys);
  }


  public static void expireDays(String key, int timeOut) {
    getObjectTemplate().expire(key, timeOut, TimeUnit.DAYS);
  }

  public static void expireMinutes(String key, int timeOut) {
    getObjectTemplate().expire(key, timeOut, TimeUnit.MINUTES);
  }

  public static Long getExpireSeconds(String key) {
    return getExpire(key, TimeUnit.SECONDS);
  }

  public static Long getExpireMinutes(String key) {
    return getExpire(key, TimeUnit.MINUTES);
  }

  public static Long getExpire(String key, TimeUnit timeUnit) {
    return getObjectTemplate().getExpire(key, timeUnit);
  }

  public static class Value {
    public static <T> void set(String key, Object value) {
      opsForValue().set(key, value);
    }

    public static <T> T get(String key, Class<T> clazz) {
      return JacksonUtil.toBean(opsForValue().get(key), clazz);
    }

    public static <T> List<T> getMulti(Collection<String> keys, Class<T> clazz) {
      return (List<T>) JacksonUtil.toCollection(JacksonUtil.toJson(opsForValue().multiGet(keys)), List.class, clazz);
    }

    public static void setMulti(Map<String, ?> values) {
      opsForValue().multiSet(values);
    }

    public static void increment(String key) {
      opsForValue().increment(key);
    }
  }

  public static class Hash {
    public static void put(String key, String fieldKey, Object value) {
      opsForHash().put(key, fieldKey, value);
    }

    public static void putAll(String key, Map<String, ? extends Object> value) {
      if (CollUtil.isEmpty(value)) return;
      opsForHash().putAll(key, value);
    }

    public static void putIfAbsent(String key, String fieldKey, Object value) {
      opsForHash().putIfAbsent(key, fieldKey, value);
    }

    public static void delete(String key, List<String> fieldKeys) {
      if (CollUtil.isEmpty(fieldKeys)) return;
      opsForHash().delete(key, fieldKeys.toArray(new Object[]{}));
    }

    public static Object get(String key, String fieldKey) {
      return opsForHash().get(key, fieldKey);
    }

    public static <T> T get(String key, String fieldKey, Class<T> clazz) {
      return JacksonUtil.toBean(opsForHash().get(key, fieldKey), clazz);
    }

    public static boolean exist(String key, String fieldKey) {
      return BooleanUtil.isTrue(opsForHash().hasKey(key, fieldKey));
    }

    public static <T> Map<String, T> multiGet(String key, List<String> fieldKeys, Class<T> clazz) {
      if (StrUtil.isBlank(key) || CollUtil.isEmpty(fieldKeys)) return Collections.emptyMap();
      List<T> data = (List<T>) JacksonUtil.toCollection(JacksonUtil.toJson(opsForHash().multiGet(key, fieldKeys)),
              List.class, clazz);
      return IntStream.range(0, fieldKeys.size()).boxed().collect(CollectorUtils.toMap(x -> fieldKeys.get(x),
              x -> data.get(x)));
    }

    public static <T> List<T> values(String key, Class<T> clazz) {
      return (List<T>) JacksonUtil.toCollection(JacksonUtil.toJson(opsForHash().values(key)), List.class, clazz);
    }

    public static java.util.Set<String> keys(String key) {
      return opsForHash().keys(key);
    }

    public static Map<String, Object> entries(String key) {
      return opsForHash().entries(key);
    }

    public static <T> Map<String, T> entries(String key, Class<T> clazz) {
      return opsForHash().entries(key).entrySet().stream().collect(Collectors.toMap(x -> x.getKey(),
              x -> JacksonUtil.toBean(x.getValue(), clazz)));
    }
  }

  public static class LIST {
    public static void rightPushAll(String key, List<? extends Object> values) {
      if (CollUtil.isEmpty(values)) return;
      opsForList().rightPushAll(key, values.toArray(new Object[values.size()]));
    }

    public static <T> T rightPop(String key, Class<T> clazz) {
      return JacksonUtil.toBean(opsForList().leftPop(key), clazz);
    }

    public static <T> T rightPop(String key, long timeout, TimeUnit unit, Class<T> clazz) {
      return JacksonUtil.toBean(opsForList().leftPop(key, timeout, unit), clazz);
    }
  }

  public static class Set {
    public static Long size(String key) {
      return opsForSet().size(key);
    }

    public static boolean isMember(String key, Object value) {
      return opsForSet().isMember(key, value);
    }

    public static Long add(String key, List<? extends Object> values) {
      if (CollUtil.isEmpty(values)) return NumberUtils.LONG_ZERO;
      return opsForSet().add(key, values.toArray(new Object[values.size()]));
    }

    public static Long remove(String key, Object... value) {
      return opsForSet().remove(key, value);
    }

    public static java.util.Set<Object> members(String key) {
      return opsForSet().members(key);
    }

    @Deprecated
    // TODO 访方法会引起 java.lang.StackOverflowError: null，目前未处理
    public static Map<Object, Boolean> isMembers(String key, List<? extends Object> values) {
      return opsForSet().isMember(key, values.toArray(new Object[]{}));
    }
  }

  public static class ZSet {
    public static Boolean addIfAbsent(String key, Object value, double score) {
      return opsForZSet().addIfAbsent(key, value, score);
    }
  }

  public static class Parser {
    public static <T extends Object> Map<String, T> parse(Map<Long, T> value) {
      if (Objects.isNull(value)) return new HashMap<>();
      return value.entrySet().stream().filter(x -> Objects.nonNull(x.getKey())).collect(HashMap::new, //
              (map, item) -> map.put(item.getKey().toString(), item.getValue()), HashMap::putAll);
    }
  }

}
